﻿using BUGCOME.Infrastructure.Attribute;
using BUGCOME.Infrastructure.Cache;
using BUGCOME.Infrastructure.Extensions;
using BUGCOME.Infrastructure.Helper;
using BUGCOME.Model.Auth;
using BUGCOME.Model.Auth.dto;
using BUGCOME.Model.Logs;
using BUGCOME.Repository;
using BUGCOME.Service.Auth.IServices;
using BUGCOME.Service.Common;
using BUGCOME.Service.Common.IServices;
using BUGCOME.ServiceCore.Resources;
using BUGCOME.ServiceCore.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Localization;
using SqlSugar.Extensions;

namespace BUGCOME.Service.Auth
{
    /// <summary>
    /// 登录服务实现类，负责处理用户登录相关业务逻辑
    /// 涵盖账号密码登录验证、手机号验证码登录、登录日志管理及登录状态统计等功能
    /// </summary>
    [AppService(ServiceType = typeof(ILoginService), ServiceLifetime = LifeTime.Transient)]
    public class LoginService : BaseService<UserLoginLog>, ILoginService
    {
        // 依赖注入的服务接口（已规范命名并添加注释）
        private readonly IUserBaseService userBaseService;          // 用户服务接口，用于用户验证、信息更新及数据查询
        private readonly IHttpContextAccessor httpContextAccessor; // HTTP上下文访问器，获取客户端IP、浏览器等信息
        private readonly IStringLocalizer<SharedResource> localizer; // 本地化资源服务，提供多语言错误提示
        private readonly ISysConfigService sysConfigService;

        // 缓存Key前缀（基于username，不修改参数）
        private const string FAIL_COUNT_KEY_PREFIX = "Login:FailCount:";

        private const string LOCK_END_TIME_KEY_PREFIX = "Login:LockEndTime:";

        /// <summary>
        /// 初始化LoginService类的新实例
        /// </summary>
        /// <param name="userBaseService">用户基础服务，必须实现IUserBaseService接口</param>
        /// <param name="httpContextAccessor">HTTP上下文访问器，必须实现IHttpContextAccessor接口</param>
        /// <param name="localizer">本地化资源服务，必须实现IStringLocalizer&lt;SharedResource&gt;接口</param>
        public LoginService(
            IUserBaseService userBaseService,
            IHttpContextAccessor httpContextAccessor,
            IStringLocalizer<SharedResource> localizer,
            ISysConfigService sysConfigService)
        {
            this.userBaseService = userBaseService;
            this.httpContextAccessor = httpContextAccessor;
            this.localizer = localizer;
            this.sysConfigService = sysConfigService;
        }

        /// <summary>
        /// 异步处理账号密码登录验证流程
        /// 包含密码加密校验、用户状态验证、登录日志记录及客户端信息采集等操作
        /// </summary>
        /// <param name="loginBody">登录请求参数，包含用户名、密码、登录IP等信息</param>
        /// <param name="loginLog">登录日志实体，用于记录登录结果及相关信息</param>
        /// <returns>登录成功的用户实体，包含用户基本信息及权限标识</returns>
        /// <exception cref="CustomException">当密码错误、用户被禁用或账号不存在时抛出</exception>
        public async Task<UserBase> LoginAsync(LoginDto loginBody, UserLoginLog loginLog)
        {
            // 密码加密处理（MD5加密，32位长度为已加密状态）
            string password = CryptoHelper.AesDecrypt(loginBody.Password, loginBody.Username + (loginBody.Uuid), loginBody.Uuid);

            // 调用用户服务进行登录验证
            var user = await userBaseService.LoginAsync(loginBody);

            // 初始化登录日志基础信息（默认状态为登录失败）
            loginLog.UserName = loginBody.Username;
            loginLog.Status = "1";
            loginLog.LoginTime = DateTime.Now;
            loginLog.Ipaddr = loginBody.LoginIP;
            loginLog.ClientId = loginBody.ClientId;

            // 获取客户端环境信息（优先使用HttpContext，否则使用备用方案）
            string browserInfo = "未知浏览器";
            string osInfo = "未知操作系统";

            if (httpContextAccessor.HttpContext != null)
            {
                var clientInfo = httpContextAccessor.HttpContext.GetClientInfo();
                browserInfo = clientInfo.ToString();
                osInfo = clientInfo.OS.ToString();
            }

            loginLog.Browser = browserInfo;
            loginLog.Os = osInfo;

            // 验证用户是否存在
            if (user == null || user.Id <= 0)
            {
                loginLog.Msg = localizer["login_pwd_error"].Value; // 密码错误提示
                await AddLoginInfoAsync(loginLog);                 // 记录失败日志
                throw new CustomException(ResultCode.LOGIN_ERROR, loginLog.Msg, false);
            }
            string passwordNew = EncryptHelper.Sha256WithMixedCase(password + Tools.Split(user.Guid, "-")[0], Tools.Split(user.Guid, "-")[0]);
            if (user.Password != passwordNew)
            {
                loginLog.Msg = localizer["login_pwd_error"].Value;
                await AddLoginInfoAsync(loginLog);
                throw new CustomException(ResultCode.LOGIN_ERROR, loginLog.Msg, false);
            }
            loginLog.UserId = user.Id;

            // 验证用户状态（1表示已禁用）
            if (user.Status == 0)
            {
                loginLog.Msg = localizer["login_user_disabled"].Value; // 用户禁用提示
                await AddLoginInfoAsync(loginLog);
                throw new CustomException(ResultCode.LOGIN_ERROR, loginLog.Msg, false);
            }

            // 记录成功登录日志
            loginLog.Status = "0";
            loginLog.Msg = "登录成功";
            await AddLoginInfoAsync(loginLog);

            // 更新用户最后登录信息（IP、时间等）
            await userBaseService.UpdateLoginInfoAsync(loginBody.LoginIP, user.Id);
            return user;
        }



        /// <summary>
        /// 异步处理手机号验证码登录流程
        /// 适用于移动端快速登录场景，支持用户绑定已有账号或创建新账号
        /// </summary>
        /// <param name="loginBody">手机登录请求参数，包含手机号、验证码等信息</param>
        /// <param name="loginLog">登录日志实体，用于记录登录结果</param>
        /// <param name="user">用户实体（可选），存在时用于绑定登录状态</param>
        /// <returns>登录成功的用户实体，若用户不存在则自动创建基础账号</returns>
        /// <exception cref="CustomException">当用户被禁用或验证码无效时抛出</exception>
        public async Task<UserBase> PhoneLoginAsync(PhoneLoginDto loginBody, UserLoginLog loginLog, UserBase user)
        {
            // 填充登录日志基础信息
            loginLog.UserName = user?.UserName ?? loginBody.PhoneNum;
            loginLog.Status = "1";
            loginLog.LoginTime = DateTime.Now;
            loginLog.Ipaddr = loginBody.LoginIP;

            // 获取客户端环境信息（优先使用HttpContext，否则使用备用方案）
            string browserInfo = "未知浏览器";
            string osInfo = "未知操作系统";

            if (httpContextAccessor.HttpContext != null)
            {
                var clientInfo = httpContextAccessor.HttpContext.GetClientInfo();
                browserInfo = clientInfo.ToString();
                osInfo = clientInfo.OS.ToString();
            }

            loginLog.Browser = browserInfo;
            loginLog.Os = osInfo;

            // 验证用户状态
            if (user != null && user.Status == 1)
            {
                loginLog.Msg = localizer["login_user_disabled"].Value;
                await AddLoginInfoAsync(loginLog);
                throw new CustomException(ResultCode.LOGIN_ERROR, loginLog.Msg, false);
            }

            // 记录成功登录日志
            loginLog.Status = "0";
            loginLog.Msg = "登录成功";
            await AddLoginInfoAsync(loginLog);

            // 异步更新用户登录信息（若用户存在）
            if (user != null)
            {
                await userBaseService.UpdateLoginInfoAsync(loginBody.LoginIP, user.Id);
            }
            return user ?? new UserBase();
        }

        /// <summary>
        /// 异步查询登录日志列表（支持分页与多条件筛选）
        /// 可根据时间范围、用户ID、登录状态、IP地址等条件进行精准查询
        /// </summary>
        /// <param name="logininfoDto">查询条件实体，包含分页参数及筛选条件</param>
        /// <returns>分页包装的登录日志集合，包含总记录数与当前页数据</returns>
        public async Task<PagedInfo<UserLoginLog>> GetLoginLogAsync(LogininfoQueryDto logininfoDto)
        {
            var exp = Expressionable.Create<UserLoginLog>();

            // 构建动态查询条件
            exp.AndIF(logininfoDto.BeginTime == null,
                it => it.LoginTime >= DateTime.Now.ToShortDateString().ParseToDateTime());
            exp.AndIF(logininfoDto.BeginTime != null,
                it => it.LoginTime >= logininfoDto.BeginTime && it.LoginTime <= logininfoDto.EndTime);
            exp.AndIF(logininfoDto.UserId.HasValue,
                it => it.UserId == logininfoDto.UserId);
            exp.AndIF(!string.IsNullOrEmpty(logininfoDto.Status),
                f => f.Status == logininfoDto.Status);
            exp.AndIF(!string.IsNullOrEmpty(logininfoDto.Ipaddr),
                f => f.Ipaddr == logininfoDto.Ipaddr);
            exp.AndIF(!string.IsNullOrEmpty(logininfoDto.UserName),
                f => f.UserName.Contains(logininfoDto.UserName));

            // 执行查询并按日志ID降序排序
            var query = Queryable().Where(exp.ToExpression())
                .OrderBy(it => it.Id, OrderByType.Desc);

            return await query.ToPageAsync(logininfoDto); // 异步分页查询
        }

        /// <summary>
        /// 异步记录登录日志到数据库
        /// 包含登录结果、客户端信息、用户标识等完整登录上下文
        /// </summary>
        /// <param name="loginLog">已填充完整信息的登录日志实体</param>
        public async Task AddLoginInfoAsync(UserLoginLog loginLog)
        {
            await InsertAsync(loginLog); // 异步插入数据库
        }

        /// <summary>
        /// 异步清空所有登录日志（危险操作）
        /// 注意：此操作将永久删除所有登录记录，无法恢复
        /// </summary>
        public async Task TruncateLogininfoAsync()
        {
            await TruncateAsync(); // 异步清空表数据
        }

        /// <summary>
        /// 异步批量删除指定ID的登录日志
        /// </summary>
        /// <param name="ids">待删除日志的ID数组</param>
        /// <returns>成功删除的记录数量</returns>
        public async Task<long> DeleteUserLoginLogByIdsAsync(long[] ids)
        {
            return await DeleteAsync(ids); // 异步批量删除
        }

        /// <summary>
        /// 检查用户是否被锁定（同步方法）
        /// 通过缓存服务验证用户状态，防止频繁登录尝试
        /// </summary>
        /// <param name="userName">待检查的用户名</param>
        /// <exception cref="CustomException">当用户被锁定时抛出，包含剩余锁定时间提示</exception>
        public void CheckLockUser(string userName)
        {
            var lockTimeStamp = CacheService.GetLockUser(userName); // 从缓存获取锁定时间戳
            var lockTime = DateTimeHelper.ToLocalTimeDateBySeconds(lockTimeStamp);
            var ts = lockTime - DateTime.Now;

            if (lockTimeStamp > 0 && ts.TotalSeconds > 0)
            {
                throw new CustomException(ResultCode.LOGIN_ERROR,
                    $"你的账号已被锁定，剩余{Math.Round(ts.TotalMinutes, 0)}分钟");
            }
        }

        /// <summary>
        /// 异步查询近7天登录日志统计数据
        /// 按日期维度统计总登录次数与去重次数（基于IP地址去重）
        /// </summary>
        /// <returns>包含日期、总次数、去重次数及星期名称的统计结果集合</returns>
        public async Task<List<StaticLoginLogDto>> GetStatiLoginlogAsync()
        {
            var time = DateTime.Now;
            var days = (time.AddMonths(1) - time).Days; // 获取当月天数
            var dayArray = Enumerable.Range(1, days)
                .Select(it => Convert.ToDateTime(time.ToString("yyyy-MM-" + it)))
                .ToList(); // 生成当月日期数组

            var queryableLeft = Context.Reportable(dayArray)
                .ToQueryable<DateTime>();

            var queryableRight = Context.Queryable<UserLoginLog>();

            // 执行左连接查询（日期维度表与登录日志表）
            var list = await Context.Queryable(queryableLeft, queryableRight, JoinType.Left,
                    (x1, x2) => x2.LoginTime.ToString("yyyy-MM-dd") == x1.ColumnName.ToString("yyyy-MM-dd"))
                .GroupBy((x1, x2) => x1.ColumnName) // 按日期分组
                .Where((x1, x2) => x1.ColumnName >= DateTime.Now.AddDays(-7) && x1.ColumnName <= DateTime.Now)
                .Select((x1, x2) => new StaticLoginLogDto
                {
                    DeRepeatNum = SqlFunc.AggregateDistinctCount(x2.Ipaddr), // 按IP去重的登录次数
                    Num = SqlFunc.AggregateCount(x2.Id),                // 总登录次数
                    Date = x1.ColumnName,
                })
                .Mapper(it => it.WeekName = Tools.GetWeekByDate(it.Date)) // 补充星期名称
                .ToListAsync(); // 异步获取查询结果

            return list;
        }

        /// <summary>
        /// 异步校验用户手机号是否已注册
        /// </summary>
        /// <param name="dto">请求参数，包含手机号与用户类型标识</param>
        /// <returns>布尔值，true表示手机号已注册，false表示未注册</returns>
        public async Task<bool> CheckUserMobileIsExistAsync(MobileCodeDto dto)
        {
            // 异步查询用户表（手机号、用户类型匹配且未删除的记录）
            return await userBaseService.Queryable()
                .Where(u => u.UserName == dto.Mobile && u.DelFlag == 0)
                .WhereIF(new[] { "1", "2", "3" }.Contains(dto.UserType),
                u => u.UserType == dto.UserType)
                .AnyAsync();
        }

        /// <summary>
        /// 检查锁定（使用原有参数中的username）
        /// </summary>
        /// <param name="userName">用户名称</param>
        /// <returns></returns>
        public void CheckLoginLock(string userName)
        {
            if (string.IsNullOrEmpty(userName))
                throw new CustomException(ResultCode.PARAM_ERROR, "用户名或用户类型不能为空");

            // 基于原有参数组合生成缓存键
            var comboKey = $"{userName}";
            var lockEndTimeKey = $"{LOCK_END_TIME_KEY_PREFIX}{comboKey}";
            var lockEndTimeKeyValue = CacheHelper.GetCache(lockEndTimeKey);
            if (lockEndTimeKeyValue == null) return;
            DateTime? lockEndTime = lockEndTimeKeyValue.ObjToDate();

            if (DateTime.Now < lockEndTime)
            {
                var remaining = lockEndTime.Value - DateTime.Now;
                var message = $"当前用户已被锁定，请在{remaining.Minutes}分{remaining.Seconds}秒后重试";
                throw new CustomException(ResultCode.LOGIN_LOCKED, message, false);
            }

            // 清理过期锁定
            CacheHelper.Remove(lockEndTimeKey);
            CacheHelper.Remove($"{FAIL_COUNT_KEY_PREFIX}{comboKey}");
        }

        /// <summary>
        /// 记录失败次数（使用原有参数）
        /// </summary>
        public void RecordLoginFail(string userName)
        {
            if (string.IsNullOrEmpty(userName))
                return;

            var comboKey = $"{userName}";
            var failCountKey = $"{FAIL_COUNT_KEY_PREFIX}{comboKey}";
            var lockEndTimeKey = $"{LOCK_END_TIME_KEY_PREFIX}{comboKey}";
            var currentFailCount = CacheHelper.GetCache(failCountKey).ObjToInt();
            currentFailCount = currentFailCount + 1;

            // 从配置获取参数（不依赖请求参数）
            var maxFailCount = GetLoginMaxFailCount();
            var lockHours = GetLoginLockHours();

            CacheHelper.SetCache(failCountKey, currentFailCount, lockHours * 60);
            if (currentFailCount >= maxFailCount)
            {
                var lockEndTime = DateTime.Now.AddHours(lockHours);
                CacheHelper.SetCache(lockEndTimeKey, lockEndTime, lockHours * 60);
            }
        }

        /// <summary>
        /// 重置失败次数（使用原有参数）
        /// </summary>
        public void ResetLoginFailCount(string userName)
        {
            if (string.IsNullOrEmpty(userName))
                return;

            var comboKey = $"{userName}";
            CacheHelper.Remove($"{FAIL_COUNT_KEY_PREFIX}{comboKey}");
            CacheHelper.Remove($"{LOCK_END_TIME_KEY_PREFIX}{comboKey}");
        }

        /// <summary>
        /// 从配置获取最大失败次数（默认10次）
        /// </summary>
        private int GetLoginMaxFailCount()
        {
            var config = sysConfigService.GetSysConfigByKey("sys.account.loginMaxFailCount");
            if (config == null || string.IsNullOrEmpty(config.ConfigValue) || !int.TryParse(config.ConfigValue, out int count))
                return 10;
            return count < 1 ? 1 : count;
        }

        /// <summary>
        /// 从配置获取锁定时长（默认1小时）
        /// </summary>
        private int GetLoginLockHours()
        {
            var config = sysConfigService.GetSysConfigByKey("sys.account.loginLockHours");
            if (config == null || string.IsNullOrEmpty(config.ConfigValue) || !int.TryParse(config.ConfigValue, out int hours))
                return 1;
            return hours < 1 ? 1 : hours;
        }
    }
}